﻿using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace Function.HotUpdate
{
    public class HotAddressable : HotStep
    {
        public override HotState GetHotState()
        {
            return HotState.UpdateAddressable;
        }

        public HotAddressable(HotContext hotContext, bool cb) : base(hotContext, cb)
        {
        }

        private List<AsyncOperationHandle> GetAllAsyncOperationHandles()
        {
            var handles = new List<AsyncOperationHandle>();

            var resourceManagerType = Addressables.ResourceManager.GetType();
            var dictionaryMember = resourceManagerType.GetField("m_AssetOperationCache",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var dictionary = dictionaryMember.GetValue(Addressables.ResourceManager) as IDictionary;

            foreach (var asyncOperationInterface in dictionary.Values)
            {
                if (asyncOperationInterface == null)
                    continue;

                var handle = typeof(AsyncOperationHandle).InvokeMember(nameof(AsyncOperationHandle),
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,
                    null, null, new object[] { asyncOperationInterface });

                handles.Add((AsyncOperationHandle)handle);
            }

            return handles;
        }

        static void ReleaseAsyncOperationHandles(List<AsyncOperationHandle> handles)
        {
            foreach (var handle in handles)
            {
                if (!handle.IsDone)
                    $"AsyncOperationHandle not completed yet. Releasing anyway!".Warn();
                while (handle.IsValid())
                {
                    Addressables.ResourceManager.Release(handle);
                }
            }
        }

        protected override IEnumerator OnExecute()
        {
            if (_restTime > 0)
            {
                yield return new WaitForSeconds(_restTime);
                _restTime = 0;
            }

            _hotContext.Info("HotAddressable", "Start");
            if (!_hotContext._needUpdate)
            {
                SetSuccess(true);
                yield return Finish();
                yield break;
            }


            // 更新相同资源包之前需要清理所有句柄
            // https://forum.unity.com/threads/how-to-unload-everything-currently-loaded-by-addressables.1121998/
            var handles = GetAllAsyncOperationHandles();
            ReleaseAsyncOperationHandles(handles);
            yield return null;


            var checkHandle = Addressables.CheckForCatalogUpdates(false);
            yield return checkHandle;
            if (checkHandle.Status != AsyncOperationStatus.Succeeded)
            {
                SetSuccess(false);
                _hotContext.Error("版本检测失败:", checkHandle.OperationException.ToString(),
                    (int)HotUpdateErrorType.CheckForCatalogUpdateFair);
                yield break;
            }

            if (checkHandle.Result.Count > 0)
            {
                var updateHandle = Addressables.UpdateCatalogs(checkHandle.Result, false);
                yield return updateHandle;
                if (updateHandle.Status != AsyncOperationStatus.Succeeded)
                {
                    SetSuccess(false);
                    _hotContext.Error("版本更新失败:", updateHandle.OperationException.ToString(),
                        (int)HotUpdateErrorType.UpdateCatalogsFair);
                    yield break;
                }

                var locators = updateHandle.Result;
                foreach (var locator in locators)
                {
                    _hotContext._updateKeys.AddRange(locator.Keys);
                }

                Addressables.Release(checkHandle);
                Addressables.Release(updateHandle);
            }
            else
            {
                var locators = Addressables.ResourceLocators;
                foreach (var locator in locators)
                {
                    _hotContext._updateKeys.AddRange(locator.Keys);
                }

                Addressables.Release(checkHandle);
            }

            SetSuccess(true);
            _hotContext.Info("HotAddressable", "End");
            yield return Finish();
        }

        public override void ResNet()
        {
            _restTime = 2f; //重试流程
            HotManager.Instance.StartCoroutine(Execute());
            base.ResNet();
        }

        public override void StopNet()
        {
            SetSuccess(false);
            HotManager.Instance.StopAllCoroutines();
        }
    }
}